//
// Created by hoyin on 2022/2/15.
//

#include "Matcher.h"

#define CONTOURS_NONE -1

Matcher::Matcher(cuda::GpuMat &target, const Size &srcSize, const Size &targetSize) {
	setTargetImage(target, targetSize);
}

void Matcher::setOriginImage(cuda::GpuMat &src, const Size& size) {
	if (src.size().empty()) return;
	if (src.size() != size && !size.empty()) {
		cuda::resize(src, gOriginImage, size);
	} else {
		gOriginImage = src.clone();
	}
}

void Matcher::setTargetImage(cuda::GpuMat &target, const Size& size) {
	if (target.size().empty()) return;
	if (target.size() != size && !size.empty()) {
		cuda::resize(target, targetImage, size);
	} else {
		gTargetImage = target.clone();
	}
}

Rect2d Matcher::templateMatchWithCUDA(double threshold, int method) {
	Mat matchResult, originGray, targetGray;
	cuda::GpuMat gMatchResult, gOriginGray, gTargetGray;
	Point minLoc, maxLoc, tarLoc;
	double minVal, maxVal;
	Rect2d result(0, 0, 0, 0);
	if (gOriginImage.empty() || targetImage.empty()) {
		printf("unavailable image.");
	} else {
		if (gOriginImage.channels() == 3) {
			cuda::cvtColor(gOriginImage, gOriginGray, CV_BGR2GRAY);
		} else {
			gOriginGray = gOriginImage;
		}
		if (gTargetImage.channels() == 3) {
			cuda::cvtColor(gTargetImage, targetGray, CV_BGR2GRAY);
		} else {
			gTargetGray = gTargetImage;
		}
	}
	gOriginGray.download(originGray);
	gTargetImage.download(targetGray);
	Ptr<cuda::TemplateMatching> alg = cuda::createTemplateMatching(originGray.type(), method);
	alg->match(gOriginGray, gTargetGray, gMatchResult);
	gMatchResult.download(matchResult);
//	matchTemplate(originGray, targetGray, matchResult, method);
//	imshow("matchresult", matchResult);
//	waitKey();
//	destroyAllWindows();
	minMaxLoc(matchResult, &minVal, &maxVal, &minLoc, &maxLoc);
	if (TM_SQDIFF == method || TM_SQDIFF_NORMED == method) {
		tarLoc = minLoc;
		if (minVal < threshold) {
			result = Rect2d(tarLoc, Point(tarLoc.x  + targetImage.cols, tarLoc.y + targetImage.rows));
//			printf("matched, response: %f\n", minVal);
		}
	} else {
		tarLoc = maxLoc;
		if (maxVal > threshold) {
			result = Rect2d(tarLoc, Point(tarLoc.x  + targetImage.cols, tarLoc.y + targetImage.rows));
//			printf("matched, response: %f\n", maxVal);
		}
	}
	return result;
}

vector<Point> Matcher::contoursMatchWithCUDA() {
	cuda::GpuMat gSrcGray, gSrcBinary, gTargetGray, gTargetBinary;
	if (gOriginImage.channels() == 3) {
		cuda::cvtColor(gOriginImage, gSrcGray, COLOR_BGR2GRAY);
	} else {
		gSrcGray = gOriginImage;
	}
	if (gTargetImage.channels() == 3) {
		cuda::cvtColor(gTargetImage, gTargetGray, COLOR_BGR2GRAY);
	} else {
		gTargetGray = gTargetImage;
	}
//	如果图像本身是二值的，则二值化就不需要了
	cuda::threshold(gSrcGray, gSrcBinary, 50, 255, THRESH_BINARY);
	cuda::threshold(gTargetGray, gTargetBinary, 50, 255, THRESH_BINARY);
	Mat swkSrcBinary, swkTgtBinary;
	gSrcBinary.download(swkSrcBinary);
	gTargetBinary.download(swkTgtBinary);
//	imshow("src", swkSrcBinary);
//	imshow("tgt", swkTgtBinary);
//	waitKey();
//	destroyAllWindows();
	Mat element = getStructuringElement(MORPH_RECT, Size(7, 7));
	Ptr<cuda::Filter> filterMRClose = cuda::createMorphologyFilter(MORPH_CLOSE, CV_8UC1, element);
	filterMRClose->apply(gSrcBinary, gSrcBinary);
	filterMRClose->apply(gTargetBinary, gTargetBinary);

	vector<vector<Point>> srcContours, targetContours;
	Mat srcBinary, targetBinary;
	gSrcBinary.download(srcBinary);
	gTargetBinary.download(targetBinary);
	findContours(srcBinary, srcContours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
	findContours(targetBinary, targetContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

	double a0min = 1;
	int a0minLoc = CONTOURS_NONE;
	for (int i = 0; i < srcContours.size(); ++i) {
		double a0 = matchShapes(srcContours[i], targetContours[0], CV_CONTOURS_MATCH_I1, 0);
//		cout << "contour: " << i << " similarity:" << a0 << endl;
		if (a0 < a0min) {
			a0min = a0;
			a0minLoc = i;
//			Rect2d targetRect = boundingRect(srcContours[i]);
//			rectangle(canvas, targetRect, Scalar(20, 20, 200), 2);
//			return targetRect;
		}
	}
//	cout << "closest: " << a0minLoc << " similarity: " << a0min << endl << endl;
	if (a0minLoc == CONTOURS_NONE) {
		vector<Point> noneRet;
		noneRet.emplace_back(0, 0);
		return noneRet;
	}
	return srcContours[a0minLoc];
}

